/*
Copyright (C) 1994-1995 Apogee Software, Ltd.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "cin_glob.h"
#include "cin_util.h"
#include "cin_def.h"
#include "cin_main.h"
#include "f_scale.h"
#include "watcom.h"
#include "lumpy.h"
#include "w_wad.h"
#include "z_zone.h"
#include <string.h>
#include <conio.h>

#include "modexlib.h"
#include "fli_glob.h"
//MED
#include "memcheck.h"

static int cin_sprtopoffset;
static int cin_invscale;

/*
===============
=
= SpawnCinematicFlic
=
===============
*/

flicevent * SpawnCinematicFlic ( char * name, boolean loop, boolean usefile )
{
   flicevent * flic;

   flic = SafeMalloc ( sizeof(flicevent) );

   // copy name of flic

   strcpy ( flic->name, name );

   flic->loop=loop;

   flic->usefile=usefile;

   return flic;
}

/*
===============
=
= SpawnCinematicSprite
=
===============
*/

spriteevent * SpawnCinematicSprite ( char * name,
                                     int duration,
                                     int numframes,
                                     int framedelay,
                                     int x,
                                     int y,
                                     int scale,
                                     int endx,
                                     int endy,
                                     int endscale
                                   )
{
   spriteevent * sprite;
   patch_t *p;

   sprite = SafeMalloc ( sizeof (spriteevent) );

   // copy name of sprite

   strcpy ( sprite->name, name );

   // copy rest of sprite information

   sprite->duration = duration;
   sprite->numframes = numframes;
   sprite->framedelay = framedelay;
   sprite->frame=0;
   sprite->frametime=framedelay;

   p=(patch_t *)W_CacheLumpNum( W_GetNumForName(sprite->name), PU_CACHE);

   sprite->x=x << FRACTIONBITS;
   sprite->y=y << FRACTIONBITS;

//   sprite->y+=(p->width-p->height)<<(FRACTIONBITS-1);

   sprite->scale=scale << FRACTIONBITS;
   sprite->dx= ( (endx-x) << FRACTIONBITS ) / duration;
   sprite->dy= ( (endy-y) << FRACTIONBITS ) / duration;
   sprite->dscale= ( (endscale-scale) << FRACTIONBITS ) / duration;

   return sprite;
}

/*
===============
=
= SpawnCinematicBack
=
===============
*/

backevent * SpawnCinematicBack ( char * name,
                                 int duration,
                                 int width,
                                 int startx,
                                 int endx,
                                 int yoffset
                               )
{
   backevent * back;

   back = SafeMalloc ( sizeof (backevent) );

   // copy name of back

   strcpy ( back->name, name );

   // copy rest of back information

   back->duration = duration;
   back->backdropwidth = width;
   back->dx = ((endx-startx) << FRACTIONBITS)/duration;
   back->currentoffset = startx << FRACTIONBITS;
   back->yoffset=yoffset;

   return back;
}

/*
===============
=
= SpawnCinematicMultiBack
=
===============
*/

backevent * SpawnCinematicMultiBack ( char * name,
                                      char * name2,
                                      int duration,
                                      int startx,
                                      int endx,
                                      int yoffset
                                    )
{
   backevent * back;
   lpic_t * pic1;
   lpic_t * pic2;

   pic1=(lpic_t *)W_CacheLumpName(name,PU_CACHE);
   pic2=(lpic_t *)W_CacheLumpName(name2,PU_CACHE);

   back = SafeMalloc ( sizeof (backevent) );

   // copy name of back

   strcpy ( back->name, name );

   // copy rest of back information

   back->duration = duration;
   back->dx = ((endx-startx) << FRACTIONBITS)/duration;
   back->currentoffset = startx << FRACTIONBITS;
   back->yoffset=yoffset;
   if (pic1->height != pic2->height)
      {
      Error("SpawnCinematicMultiBack: heights are not the same\n"
            "                         name1=%s name2=%s\n",name,name2);
      }
   back->backdropwidth=pic1->width+pic2->width;
   back->height=pic1->height;
   back->data=SafeMalloc (back->backdropwidth*back->height);
   memcpy( back->data, &(pic1->data), pic1->width*pic1->height);
   memcpy( back->data+(pic1->width*pic1->height),
           &(pic2->data),
           pic2->width*pic2->height
         );
   return back;
}

/*
===============
=
= SpawnCinematicPalette
=
===============
*/

paletteevent * SpawnCinematicPalette ( char * name )
{
   paletteevent * palette;

   palette = SafeMalloc ( sizeof (paletteevent) );

   // copy name of palette

   strcpy ( palette->name, name );

   return palette;
}


/*
=================
=
= ScaleFilmPost
=
=================
*/
void ScaleFilmPost (byte * src, byte * buf)
{
   int  offset;
   int  length;
   int  topscreen;
   int  bottomscreen;


   offset=*(src++);
   for (;offset!=255;)
      {
      length=*(src++);
      topscreen = cin_sprtopoffset + (cin_invscale*offset);
      bottomscreen = topscreen + (cin_invscale*length);
      cin_yl = (topscreen+FRACTIONUNIT-1)>>FRACTIONBITS;
      cin_yh = (bottomscreen-FRACTIONUNIT)>>FRACTIONBITS;
      if (cin_yh >= MAXSCREENHEIGHT)
         cin_yh = MAXSCREENHEIGHT-1;
      if (cin_yl < 0)
         cin_yl = 0;
      if (cin_yl <= cin_yh)
         {
         cin_source=src-offset;
         R_DrawFilmColumn (buf);
         }
      src+=length;
      offset=*(src++);
      }

}

/*
=================
=
= DrawFlic
=
=================
*/
void DrawFlic ( flicevent * flic )
{
   byte * curpal;
   byte * buf;
   char flicname[40];

   curpal = SafeMalloc (768);

   CinematicGetPalette (curpal);

   DrawFadeout ( );

   if (flic->usefile==false)
      {
      buf=W_CacheLumpName(flic->name,PU_CACHE);
      strcpy(flicname,flic->name);
      }
   else
      {
      strcpy(flicname,flic->name);
      strcat(flicname,".fli");
      }

// med
//   PlayFlic ( flicname, buf, flic->usefile, flic->loop);

   if (flic->loop==true)
      ClearCinematicAbort();

   DrawFadeout ( );

   DrawBlankScreen ( );

   VL_SetVGAPlaneMode ();

   CinematicSetPalette (curpal);

   SafeFree (curpal);
   GetCinematicTics ();
   GetCinematicTics ();
}

/*
=================
=
= PrecacheFlic
=
=================
*/

void PrecacheFlic (flicevent * flic)
{
   if (flic->usefile==false)
      {
      W_CacheLumpName(flic->name,PU_CACHE);
      }
}

/*
===============
=
= DrawCinematicBackground
=
===============
*/

void DrawCinematicBackground ( backevent * back )
{
   byte * src;
   byte * buf;
   lpic_t * pic;
   int i;
   int plane;
   int offset;
   int height;

   pic=(lpic_t *)W_CacheLumpName(back->name,PU_CACHE);

   height = pic->height;
   if (height+back->yoffset>MAXSCREENHEIGHT)
      height=MAXSCREENHEIGHT-back->yoffset;

   if (height!=MAXSCREENHEIGHT)
      DrawClearBuffer ();

   for (plane=0;plane<4;plane++)
      {
      buf=(byte *)bufferofs+ylookup[back->yoffset];
      offset=(back->currentoffset>>FRACTIONBITS)+plane;

      VGAWRITEMAP(plane);

      for (i=plane;i<MAXSCREENWIDTH;i+=4,offset+=4,buf++)
         {
         if (offset>=back->backdropwidth)
            src=&(pic->data) + ( (offset - back->backdropwidth) * (pic->height) );
         else if (offset<0)
            src=&(pic->data) + ( (offset + back->backdropwidth) * (pic->height) );
         else
            src=&(pic->data) + ( offset * (pic->height) );
         DrawFilmPost(buf,src,height);
         }
      }
}

/*
===============
=
= DrawCinematicMultiBackground
=
===============
*/

void DrawCinematicMultiBackground ( backevent * back )
{
   byte * src;
   byte * buf;
   int i;
   int plane;
   int offset;
   int height;

   height = back->height;
   if (height+back->yoffset>MAXSCREENHEIGHT)
      height=MAXSCREENHEIGHT-back->yoffset;

   if (height!=MAXSCREENHEIGHT)
      DrawClearBuffer ();

   for (plane=0;plane<4;plane++)
      {
      buf=(byte *)bufferofs+ylookup[back->yoffset];
      offset=(back->currentoffset>>FRACTIONBITS)+plane;

      VGAWRITEMAP(plane);

      for (i=plane;i<MAXSCREENWIDTH;i+=4,offset+=4,buf++)
         {
         if (offset>=back->backdropwidth)
            src=back->data + ( (offset - back->backdropwidth) * (back->height) );
         else if (offset<0)
            src=back->data + ( (offset + back->backdropwidth) * (back->height) );
         else
            src=back->data + ( offset * (back->height) );
         DrawFilmPost(buf,src,height);
         }
      }
}

/*
===============
=
= DrawCinematicBackdrop
=
===============
*/

void DrawCinematicBackdrop ( backevent * back )
{
   byte * src;
   byte * shape;
   byte * buf;
   patch_t * p;
   int i;
   int plane;
   int offset;
   int postoffset;
   int postlength;
   int toppost;

   shape=W_CacheLumpName(back->name,PU_CACHE);
   p=(patch_t *)shape;

   toppost=-p->topoffset+back->yoffset;

   for (plane=0;plane<4;plane++)
      {
      buf=(byte *)bufferofs;
      offset=(back->currentoffset>>FRACTIONBITS)+plane;

      VGAWRITEMAP(plane);

      for (i=plane;i<MAXSCREENWIDTH;i+=4,offset+=4,buf++)
         {
         if (offset>=back->backdropwidth)
            src = shape + p->collumnofs[offset - back->backdropwidth];
         else if (offset<0)
            src=shape + p->collumnofs[offset + back->backdropwidth];
         else
            src = shape + p->collumnofs[offset];

         postoffset=*(src++);
         for (;postoffset!=255;)
            {
            postlength=*(src++);
            DrawFilmPost(buf + ylookup[toppost+postoffset],src,postlength);
            src+=postlength;
            postoffset=*(src++);
            }
         }
      }
}

/*
=================
=
= PrecacheBack
=
=================
*/
void PrecacheBack ( backevent * back )
{
   W_CacheLumpName( back->name, PU_CACHE);
}


/*
=================
=
= DrawCinematicSprite
=
=================
*/
void DrawCinematicSprite ( spriteevent * sprite )
{
   byte   *shape;
   int    frac;
   patch_t *p;
   int    x1,x2;
   int    tx;
   int    xcent;
   byte * buf;
   int    height;

   height = sprite->scale >> FRACTIONBITS;

   if (height<2)
      return;

   shape=W_CacheLumpNum( W_GetNumForName(sprite->name)+sprite->frame, PU_CACHE);
   p=(patch_t *)shape;

   cin_ycenter=sprite->y >> FRACTIONBITS;
   cin_invscale = (height<<FRACTIONBITS)/p->origsize;
   buf=(byte *)bufferofs;
   tx=-p->leftoffset;
   xcent=(sprite->x & 0xffff0000)-(height<<(FRACTIONBITS-1))+(FRACTIONUNIT>>1);

//
// calculate edges of the shape
//
   x1 = (xcent+(tx*cin_invscale))>>FRACTIONBITS;
   if (x1 >= MAXSCREENWIDTH)
      return;               // off the right side
   tx+=p->width;
   x2 = ((xcent+(tx*cin_invscale)) >>FRACTIONBITS) - 1 ;
   if (x2 < 0)
      return;         // off the left side

   cin_iscale=(p->origsize<<FRACTIONBITS)/height;

   if (x1<0)
      {
      frac=cin_iscale*(-x1);
      x1=0;
      }
   else
      frac=0;
   x2 = x2 >= MAXSCREENWIDTH ? (MAXSCREENWIDTH-1) : x2;

   cin_texturemid = (((p->origsize>>1)+p->topoffset)<<FRACTIONBITS)+(FRACTIONUNIT>>1);
   cin_sprtopoffset = (cin_ycenter<<16) - FixedMul(cin_texturemid,cin_invscale);

   for (; x1<=x2 ; x1++, frac += cin_iscale)
     {
     VGAWRITEMAP(x1&3);
     ScaleFilmPost(((p->collumnofs[frac>>FRACTIONBITS])+shape),buf+(x1>>2));
     }
}

/*
=================
=
= PrecacheCinematicSprite
=
=================
*/
void PrecacheCinematicSprite ( spriteevent * sprite )
{
   int i;

   for (i=0;i<sprite->numframes;i++)
      {
      W_CacheLumpNum( W_GetNumForName(sprite->name)+i, PU_CACHE);
      }
}


/*
=================
=
= DrawPalette
=
=================
*/

void DrawPalette (paletteevent * event)
{
   byte * pal;

   pal=W_CacheLumpName(event->name,PU_CACHE);
   XFlipPage ();
   CinematicSetPalette (pal);
}

/*
=================
=
= PrecachePalette
=
=================
*/

void PrecachePalette (paletteevent * event)
{
   W_CacheLumpName(event->name,PU_CACHE);
}


/*
=================
=
= DrawFadeout
=
=================
*/
#define FADEOUTTIME 20

void DrawFadeout ( void )
{
   byte origpal[768];
   byte newpal[768];
   int      i,j;

   CinematicGetPalette (&origpal[0]);
   for (j = 0; j < FADEOUTTIME; j++)
      {
      for (i = 0; i < 768; i++)
         {
         newpal[i] = ( origpal[i] * (FADEOUTTIME - j - 1) ) / FADEOUTTIME;
         }
      WaitVBL();
      CinematicSetPalette (&newpal[0]);
      CinematicDelay();
      }
   VL_ClearVideo (0);
   GetCinematicTics ();
   GetCinematicTics ();
}

/*
=================
=
= DrawBlankScreen
=
=================
*/
void DrawBlankScreen ( void )
{
   VL_ClearVideo (0);
}

/*
=================
=
= DrawClearBuffer
=
=================
*/
void DrawClearBuffer ( void )
{
  VGAMAPMASK(15);
  memset((byte *)bufferofs,0,SCREENBWIDE*MAXSCREENHEIGHT);
}

/*
===============
=
= UpdateCinematicBack
=
===============
*/

boolean UpdateCinematicBack ( backevent * back )
{
   back->duration--;

   if (back->duration<0)
      return false;

   back->currentoffset += back->dx;

   return true;
}

/*
=================
=
= UpdateCinematicSprite
=
=================
*/
boolean UpdateCinematicSprite ( spriteevent * sprite )
{
   sprite->duration--;

   if (sprite->duration<0)
      return false;

   sprite->framedelay--;

   if (sprite->framedelay==0)
      {
      sprite->frame++;
      if (sprite->frame==sprite->numframes)
         sprite->frame=0;
      sprite->framedelay=sprite->frametime;
      }

   sprite->x+=sprite->dx;
   sprite->y+=sprite->dy;
   sprite->scale+=sprite->dscale;

   return true;
}

/*
=================
=
= UpdateCinematicEffect
=
=================
*/
boolean UpdateCinematicEffect ( enum_eventtype type, void * effect )
{
   switch (type)
      {
      case background_noscrolling:
      case background_scrolling:
      case backdrop_scrolling:
      case backdrop_noscrolling:
      case background_multi:
         return UpdateCinematicBack ( (backevent *) effect );
         break;
      case sprite_background:
      case sprite_foreground:
         return UpdateCinematicSprite ( (spriteevent *) effect );
         break;
      case flic:
         return true;
         break;
      case palette:
      case fadeout:
      case blankscreen:
      case clearbuffer:
         return true;
         break;
      case cinematicend:
         cinematicdone=true;
         return true;
         break;
      }
   return true;
}
/*
=================
=
= DrawCinematicEffect
=
=================
*/
boolean DrawCinematicEffect ( enum_eventtype type, void * effect )
{
   switch (type)
      {
      case background_noscrolling:
      case background_scrolling:
         DrawCinematicBackground ( (backevent *) effect );
         return true;
         break;
      case background_multi:
         DrawCinematicMultiBackground ( (backevent *) effect );
         return true;
         break;
      case backdrop_scrolling:
      case backdrop_noscrolling:
         DrawCinematicBackdrop ( (backevent *) effect );
         return true;
         break;
      case sprite_background:
      case sprite_foreground:
         DrawCinematicSprite ( (spriteevent *) effect );
         return true;
         break;
      case flic:
         DrawFlic ( (flicevent *) effect );
         return false;
         break;
      case palette:
         DrawPalette ( (paletteevent *) effect );
         return false;
         break;
      case fadeout:
         DrawFadeout ();
         return false;
         break;
      case blankscreen:
         DrawBlankScreen ();
         return false;
         break;
      case clearbuffer:
         DrawClearBuffer ();
         return false;
         break;
      case cinematicend:
         return true;
         break;
      }
   return true;
}

/*
=================
=
= PrecacheCinematicEffect
=
=================
*/
void PrecacheCinematicEffect ( enum_eventtype type, void * effect )
{
   switch (type)
      {
      case background_noscrolling:
      case background_scrolling:
      case backdrop_scrolling:
      case backdrop_noscrolling:
         PrecacheBack ( (backevent *) effect );
         break;
      case sprite_background:
      case sprite_foreground:
         PrecacheCinematicSprite ( (spriteevent *) effect );
         break;
      case palette:
         PrecachePalette ( (paletteevent *) effect );
         break;
      case flic:
         PrecacheFlic ( (flicevent *) effect );
         break;
      }
}

/*
===============
=
= ProfileDisplay
=
===============
*/

void ProfileDisplay ( void )
{
   byte * buf;
   int i;
   int plane;
   byte src[200];

   DrawClearBuffer ();

   for (plane=0;plane<4;plane++)
      {
      buf=(byte *)bufferofs;
      VGAWRITEMAP(plane);

      for (i=plane;i<MAXSCREENWIDTH;i+=4,buf++)
         {
         DrawFilmPost(buf,&src[0],200);
         }
      }
}

/*
===============
=
= DrawPostPic
=
===============
*/

void DrawPostPic ( int lumpnum )
{
   byte * src;
   byte * buf;
   lpic_t * pic;
   int i;
   int plane;
   int height;

   pic=(lpic_t *)W_CacheLumpNum(lumpnum,PU_CACHE);

   height = pic->height;

   for (plane=0;plane<4;plane++)
      {
      buf=(byte *)bufferofs;

      src=&(pic->data) + (plane*pic->height);

      VGAWRITEMAP(plane);

      for (i=plane;i<MAXSCREENWIDTH;i+=4,src+=(pic->height<<2),buf++)
         {
         DrawFilmPost(buf,src,height);
         }
      }
}

